#define LOG_TAG "RTMPSink"
#define LOG_NTRACE 1
#define LOG_NDEBUG 0
#include "debug.h"

#include "RTMPSink.h"
#include "librtmp/amf.h"
#include "librtmp/rtmp.h"
#include <errno.h>
#include <malloc.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

#define RTMPSINK_VERSION 0x000001

/* configurations */
#define REAL_TIMESTAMP

struct MetaData {
	int codec_type;
	unsigned int timestamp;
};

struct VideoMetaData {
	struct MetaData common;
	unsigned int framerate;
};

struct AudioMetaData {
	struct MetaData common;
	unsigned int samplerate;
	unsigned int channelconfig;
	unsigned int samplebits;
};

struct H264MetaData {
	struct VideoMetaData common;
	unsigned char *sps;
	unsigned int sps_len;
	unsigned char *pps;
	unsigned int pps_len;
	unsigned char *start_code;
	unsigned int start_code_len;
};

struct AACMetaData {
	struct AudioMetaData common;
	unsigned char *audio_specific_config;
	unsigned int audio_specific_config_len;
};

static struct H264MetaData *_H264MetaData_Create()
{
	FUNC_ENTER();

	struct H264MetaData *h264metadata = (struct H264MetaData *)malloc(sizeof(*h264metadata));
	if (!h264metadata) {
		goto END;
	}

	((struct MetaData *)h264metadata)->codec_type = CODECTYPE_VIDEO_H264;
	((struct MetaData *)h264metadata)->timestamp = 0;
	((struct VideoMetaData*)h264metadata)->framerate = 30;

	h264metadata->sps = NULL;
	h264metadata->sps_len = 0;
	h264metadata->pps = NULL;
	h264metadata->pps_len = 0;
	h264metadata->start_code = NULL;
	h264metadata->start_code_len = 0;

END:
	FUNC_EXIT();
	return h264metadata;
}

static int _H264MetaData_Destroy(struct H264MetaData *h264metadata)
{
	FUNC_ENTER();
	int ret = 0;

	if (h264metadata == NULL) {
		ERROR_PRINT("h264 metadata is null");
		goto END;
	}
	if (h264metadata->sps) {
		free(h264metadata->sps);
		h264metadata->sps = NULL;
	}

	if (h264metadata->pps) {
		free(h264metadata->pps);
		h264metadata->pps = NULL;
	}

	if (h264metadata->start_code) {
		free(h264metadata->start_code);
		h264metadata->start_code = NULL;
	}

	free(h264metadata);

END:
	FUNC_EXIT();
	return ret;
}

static struct AACMetaData *_AACMetaData_Create()
{
	FUNC_ENTER();

	struct AACMetaData *aacmetadata = (struct AACMetaData *)malloc(sizeof(*aacmetadata));
	if (!aacmetadata) {
		goto END;
	}

	((struct MetaData *)aacmetadata)->codec_type = CODECTYPE_AUDIO_AAC;
	((struct MetaData *)aacmetadata)->timestamp = 0;
	((struct AudioMetaData *)aacmetadata)->samplerate = 22050;

	aacmetadata->audio_specific_config = NULL;
	aacmetadata->audio_specific_config_len = 0;

END:
	FUNC_EXIT();
	return aacmetadata;
}

static int _AACMetaData_Destroy(struct AACMetaData *aacmetadata)
{
	FUNC_ENTER();
	int ret = 0;

	if (!aacmetadata) {
		goto END;
	}

	if (aacmetadata->audio_specific_config) {
		free(aacmetadata->audio_specific_config);
	}

	free(aacmetadata);

END:
	FUNC_EXIT();
	return ret;
}

static struct MetaData * _MetaData_Create(int codec_type)
{
	FUNC_ENTER();
	struct MetaData *metadata = NULL;

	switch (codec_type) {
	case CODECTYPE_VIDEO_H264:
		metadata = (struct MetaData *)_H264MetaData_Create();
		break;
	case CODECTYPE_AUDIO_AAC:
		metadata = (struct MetaData *)_AACMetaData_Create();
		break;
	default:
		break;
	}

	FUNC_EXIT();
	return metadata;
}

static int _MetaData_Destroy(struct MetaData *metadata)
{
	FUNC_ENTER();
	int ret = 0;

	if (metadata == NULL) {
		ERROR_PRINT("metadata is null");
		goto END;
	}

	if (metadata->codec_type == CODECTYPE_VIDEO_H264) {
		ret = _H264MetaData_Destroy((struct H264MetaData *)metadata);
	} else if (metadata->codec_type == CODECTYPE_AUDIO_AAC) {
		ret = _AACMetaData_Destroy((struct AACMetaData *)metadata);
	} else {
		free(metadata);
	}

END:
	FUNC_EXIT();
	return ret;
}

struct RTMPSink {
	char *mUrl;
	int video_first_packet;
	int audio_first_packet;
	struct MetaData * video_metadata;
	struct MetaData * audio_metadata;
	RTMP * rtmp;
	pthread_mutex_t mutex;
	RTMPPacket * video_metadata_packet;
	unsigned int video_metadata_packet_size;
	RTMPPacket * video_media_packet;
	unsigned int video_media_packet_size;
	RTMPPacket * audio_metadata_packet;
	unsigned int audio_metadata_packet_size;
	RTMPPacket * audio_media_packet;
	unsigned int audio_media_packet_size;
};

static inline long _Clock(int is_audio) {
#ifdef REAL_TIMESTAMP
	static long start[2] = {0, 0};
	struct timeval tv;
	gettimeofday(&tv, NULL);
	if (start[!!is_audio] == 0) {
		start[!!is_audio] = tv.tv_sec * 1000 + tv.tv_usec / 1000;
		return 0;
	} else {
		return tv.tv_sec * 1000 + tv.tv_usec / 1000 - start[!!is_audio];
	}
#else
	static long start[2] = {-40, 0};
	if (!is_audio) {
		start[!!is_audio] += 40;
	}

	return start[!!is_audio];
#endif
}

void * RTMPSink_Create(const char *url, int video_codec_type, int audio_codec_type)
{
	FUNC_ENTER();
	struct RTMPSink *rtmp_sink = NULL;

	if (url == NULL) {
		ERROR_PRINT("url is null");
		goto END;
	}

	if (video_codec_type < CODECTYPE_VIDEO_START || video_codec_type >= CODECTYPE_VIDEO_END) {
		ERROR_PRINT("invalid video codec type");
		goto END;
	}

	if (audio_codec_type < CODECTYPE_AUDIO_START || audio_codec_type >= CODECTYPE_AUDIO_END) {
		ERROR_PRINT("invalid audio codec type");
		goto END;
	}

	rtmp_sink = (struct RTMPSink *)malloc(sizeof(*rtmp_sink));
	if (!rtmp_sink) {
		ERROR_PRINT("no memory");
		goto END;
	}

	/* init fields */
	rtmp_sink->mUrl = strdup(url);
	rtmp_sink->video_first_packet = 1;
	rtmp_sink->audio_first_packet = 1;
	rtmp_sink->rtmp = NULL;
	rtmp_sink->video_metadata = NULL;
	rtmp_sink->audio_metadata = NULL;
	rtmp_sink->video_metadata_packet = NULL;
	rtmp_sink->video_metadata_packet_size = 0;
	rtmp_sink->video_media_packet = NULL;
	rtmp_sink->video_media_packet_size = 0;
	rtmp_sink->audio_metadata_packet = NULL;
	rtmp_sink->audio_metadata_packet_size = 0;
	rtmp_sink->audio_media_packet = NULL;
	rtmp_sink->audio_media_packet_size = 0;

	rtmp_sink->rtmp = RTMP_Alloc();
	if (!rtmp_sink->rtmp) {
		ERROR_PRINT("no memory");
		goto RELEASE_RESOURCE;
	}

	rtmp_sink->video_metadata = _MetaData_Create(video_codec_type);
	if (rtmp_sink->video_metadata == NULL) {
		ERROR_PRINT("video metadata create failed");
		goto RELEASE_RESOURCE;
	}

	rtmp_sink->audio_metadata = _MetaData_Create(audio_codec_type);
	if (rtmp_sink->audio_metadata == NULL) {
		ERROR_PRINT("audio metadata create failed");
		goto RELEASE_RESOURCE;
	}

	RTMP_Init(rtmp_sink->rtmp);

	/* set flags */
	rtmp_sink->rtmp->Link.timeout = 10;
	rtmp_sink->rtmp->Link.lFlags |= RTMP_LF_LIVE;

	if (!RTMP_SetupURL(rtmp_sink->rtmp, (char *)rtmp_sink->mUrl)) {
		ERROR_PRINT("setup url failed");
		goto RELEASE_RESOURCE;
	}

	RTMP_EnableWrite(rtmp_sink->rtmp);

	if (!RTMP_Connect(rtmp_sink->rtmp, NULL)) {
		ERROR_PRINT("connect failed");
		goto RELEASE_RESOURCE;
	}

	if (!RTMP_ConnectStream(rtmp_sink->rtmp, 0)) {
		ERROR_PRINT("connect stream failed");
		goto RELEASE_RESOURCE;
	}

	if (pthread_mutex_init(&rtmp_sink->mutex, NULL) != 0) {
		ERROR_PRINT("mutex init failed");
		goto RELEASE_RESOURCE;
	}

	goto END;
RELEASE_RESOURCE:
	if (rtmp_sink) {
		if (rtmp_sink->mUrl) {
			free(rtmp_sink->mUrl);
		}
		if (rtmp_sink->rtmp) {
			if (RTMP_IsConnected(rtmp_sink->rtmp)) {
				RTMP_Close(rtmp_sink->rtmp);
			}
			RTMP_Free(rtmp_sink->rtmp);
		}
		if (rtmp_sink->video_metadata) {
			if (_MetaData_Destroy(rtmp_sink->video_metadata)) {
				ERROR_PRINT("video metadata destroy failed");
			}
		}
		if (rtmp_sink->audio_metadata) {
			if (_MetaData_Destroy(rtmp_sink->audio_metadata)) {
				ERROR_PRINT("audio metadata destroy failed");
			}
		}
		free(rtmp_sink);
		rtmp_sink = NULL;
	}

END:
	FUNC_EXIT();
	return rtmp_sink;
}

int RTMPSink_Destroy(void **_rtmp_sink)
{
	FUNC_ENTER();
	int ret = 0;

	struct RTMPSink *rtmp_sink = *(struct RTMPSink **)_rtmp_sink;
	if (!rtmp_sink) {
		ERROR_PRINT("rtmp sink is null");
		ret = -EINVAL;
		goto END;
	}

	ret = pthread_mutex_lock(&rtmp_sink->mutex);
	if (ret != 0) {
		ERROR_PRINT("lock rtmp sink failed");
		goto END;
	}

	if (rtmp_sink->rtmp) {
		if (RTMP_IsConnected(rtmp_sink->rtmp)) {
			RTMP_Close(rtmp_sink->rtmp);
		}
		RTMP_Free(rtmp_sink->rtmp);
		rtmp_sink->rtmp = NULL;
	}

	if (rtmp_sink->video_metadata) {
		_MetaData_Destroy(rtmp_sink->video_metadata);
		rtmp_sink->video_metadata = NULL;
	}

	if (rtmp_sink->audio_metadata) {
		_MetaData_Destroy(rtmp_sink->audio_metadata);
		rtmp_sink->audio_metadata = NULL;
	}

	if (rtmp_sink->video_metadata_packet) {
		free(rtmp_sink->video_metadata_packet);
		rtmp_sink->video_metadata_packet = NULL;
	}

	if (rtmp_sink->audio_metadata_packet) {
		free(rtmp_sink->audio_metadata_packet);
		rtmp_sink->audio_metadata_packet = NULL;
	}

	if (rtmp_sink->video_media_packet) {
		free(rtmp_sink->video_media_packet);
		rtmp_sink->video_media_packet = NULL;
	}

	if (rtmp_sink->audio_media_packet) {
		free(rtmp_sink->audio_media_packet);
		rtmp_sink->audio_media_packet = NULL;
	}

	ret = pthread_mutex_unlock(&rtmp_sink->mutex);
	if (ret != 0) {
		ERROR_PRINT("unlock rtmp sink failed");
		//goto END;
	}

	ret = pthread_mutex_destroy(&rtmp_sink->mutex);
	if (ret != 0) {
		ERROR_PRINT("mutex destroy failed");
	}

	free(rtmp_sink);
	*_rtmp_sink = NULL;
END:
	FUNC_EXIT();

	return ret;
}

static int _RTMPSink_SetParameterVideoH264(struct RTMPSink *rtmp_sink, int parameter_type, const void *parameter, unsigned int parameter_size)
{
	FUNC_ENTER();
	int ret = 0;

	struct H264MetaData *h264metadata = (struct H264MetaData *)rtmp_sink->video_metadata;
	switch (parameter_type) {
	case PARAMETER_VIDEO_H264_SPS:
		h264metadata->sps = (unsigned char *)realloc(h264metadata->sps, parameter_size);
		if (h264metadata->sps) {
			memcpy(h264metadata->sps, parameter, parameter_size);
			h264metadata->sps_len = parameter_size;
		} else {
			ERROR_PRINT("no memory");
			goto END;
		}
		break;
	case PARAMETER_VIDEO_H264_PPS:
		h264metadata->pps = (unsigned char *)realloc(h264metadata->pps,parameter_size);
		if (h264metadata->pps) {
			memcpy(h264metadata->pps, parameter, parameter_size);
			h264metadata->pps_len = parameter_size;
		} else {
			ERROR_PRINT("no memory");
			goto END;
		}
		break;
	case PARAMETER_VIDEO_H264_STARTCODE:
		h264metadata->start_code = (unsigned char *)realloc(h264metadata->start_code,parameter_size);
		if (h264metadata->start_code) {
			memcpy(h264metadata->start_code, parameter, parameter_size);
			h264metadata->start_code_len = parameter_size;
		} else {
			ERROR_PRINT("no memory");
			goto END;
		}
		break;
	}

	/* update metadata packet */
	if (h264metadata->sps && h264metadata->pps) {
		unsigned int video_metadata_packet_size = sizeof(RTMPPacket) + RTMP_MAX_HEADER_SIZE + 5 + 5 + 3 + 3 + h264metadata->sps_len + h264metadata->pps_len;
		if (video_metadata_packet_size > rtmp_sink->video_metadata_packet_size) {
			rtmp_sink->video_metadata_packet = (RTMPPacket *)realloc(rtmp_sink->video_metadata_packet, video_metadata_packet_size);
			if (!rtmp_sink->video_metadata_packet) {
				rtmp_sink->video_metadata_packet_size = 0;
				ERROR_PRINT("no memory");
				goto END;
			}
		}
		rtmp_sink->video_metadata_packet_size = video_metadata_packet_size;

		/* update fields */
		rtmp_sink->video_metadata_packet->m_body = (char *)rtmp_sink->video_metadata_packet + sizeof(RTMPPacket) + RTMP_MAX_HEADER_SIZE;
		rtmp_sink->video_metadata_packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;
		rtmp_sink->video_metadata_packet->m_nBodySize = 5 + 5 + 3 + 3 + h264metadata->sps_len + h264metadata->pps_len;
		rtmp_sink->video_metadata_packet->m_nChannel = 0x04;
		rtmp_sink->video_metadata_packet->m_nTimeStamp = 0;
		rtmp_sink->video_metadata_packet->m_hasAbsTimestamp = 0;
		rtmp_sink->video_metadata_packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
		rtmp_sink->video_metadata_packet->m_nInfoField2 = rtmp_sink->rtmp->m_stream_id;

		unsigned char *packet_body = (unsigned char *)rtmp_sink->video_metadata_packet->m_body;
		/* VIDEODATA */
		/*
		 * [4b] FrameType: key frame
		 * [4b] Codec ID: AVC
		 */
		*packet_body++ = 0x17;
		/* AVCVIDEOPACKET */
		/*
		 * [8 b] AVPacket Type: AVC Sequence Header
		 * [24b] Composition Time: 0
		 */
		*packet_body++ = 0x00;
		*packet_body++ = 0x00;
		*packet_body++ = 0x00;
		*packet_body++ = 0x00;
		/* AVCDecoderConfigurationRecord */
		/*
		 * [8b] Configuration Version: 1
		 * [8b] AVC Profile Indication
		 * [8b] Profile Compatibility
		 * [8b] AVC Level Indication
		 * [6b] reserved: 111111
		 * [2b] bytes length minus 1 of nalu size expression
		 * [3b] reserved: 111
		 * [5b] sps count: 1 generally
		 * [2B] sps_size
		 * [nB] sps
		 * [8b] pps count: 1
		 * [2B] pps_size
		 * [nB] pps
		 */
		*packet_body++ = 0x01;
		*packet_body++ = h264metadata->sps[1];
		*packet_body++ = h264metadata->sps[2];
		*packet_body++ = h264metadata->sps[3];
		*packet_body++ = 0xFF;
		/* sps */
		*packet_body++ = (0xE0 | 1);
		*packet_body++ = ((h264metadata->sps_len >> 8) & 0xFF);
		*packet_body++ = (h264metadata->sps_len & 0xFF);
		memcpy(packet_body, h264metadata->sps, h264metadata->sps_len);
		packet_body += h264metadata->sps_len;
		/* pps */
		*packet_body++ = 1;
		*packet_body++ = ((h264metadata->pps_len >> 8) & 0xFF);
		*packet_body++ = (h264metadata->pps_len & 0xFF);
		memcpy(packet_body, h264metadata->pps, h264metadata->pps_len);
	}
END:
	FUNC_EXIT();
	return ret;
}

static int _RTMPSink_SetParameterAudioAAC(struct RTMPSink *rtmp_sink, int parameter_type, const void *parameter, int parameter_size)
{
	FUNC_ENTER();
	int ret = 0;

	struct AACMetaData *aacmetadata = (struct AACMetaData *)rtmp_sink->audio_metadata;;
	switch (parameter_type) {
	case PARAMETER_AUDIO_AAC_AUDIOSPECIFICCONFIG:
		aacmetadata->audio_specific_config = (unsigned char *)realloc(aacmetadata->audio_specific_config, parameter_size);
		if (aacmetadata->audio_specific_config) {
			memcpy(aacmetadata->audio_specific_config, parameter, parameter_size);
			aacmetadata->audio_specific_config_len = parameter_size;
		} else {
			ERROR_PRINT("no memory");
			goto END;
		}
		break;
	}

	if (aacmetadata->audio_specific_config) {
		unsigned int audio_metadata_packet_size = sizeof(RTMPPacket) + RTMP_MAX_HEADER_SIZE + 2 + aacmetadata->audio_specific_config_len;
		if (audio_metadata_packet_size > rtmp_sink->audio_metadata_packet_size) {
			rtmp_sink->audio_metadata_packet = (RTMPPacket *)realloc(rtmp_sink->audio_metadata_packet, audio_metadata_packet_size);
			if (!rtmp_sink->audio_metadata_packet) {
				rtmp_sink->audio_metadata_packet_size = 0;
				ERROR_PRINT("no memory");
				goto END;
			}
		}
		rtmp_sink->audio_metadata_packet_size = audio_metadata_packet_size;

		/* update fields */
		rtmp_sink->audio_metadata_packet->m_body = (char *)rtmp_sink->audio_metadata_packet + sizeof(RTMPPacket) + RTMP_MAX_HEADER_SIZE;
		rtmp_sink->audio_metadata_packet->m_packetType = RTMP_PACKET_TYPE_AUDIO;
		rtmp_sink->audio_metadata_packet->m_nBodySize = 2 + aacmetadata->audio_specific_config_len;
		rtmp_sink->audio_metadata_packet->m_nChannel = 0x04;
		rtmp_sink->audio_metadata_packet->m_nTimeStamp = 0;
		rtmp_sink->audio_metadata_packet->m_hasAbsTimestamp = 0;
		rtmp_sink->audio_metadata_packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
		rtmp_sink->audio_metadata_packet->m_nInfoField2 = rtmp_sink->rtmp->m_stream_id;

		unsigned char *packet_body = (unsigned char *)rtmp_sink->audio_metadata_packet->m_body;
		/* AUDIODATA */
		/*
		 * [4b] SoundFormat: 10: AAC
		 * [2b] SoundRate: 3: 44 kHz
		 * [1b] SoundSize: 1: snd16Bits
		 * [1b] SoundType: 1: sndStereo
		 */
		*packet_body++ = 0xAF;/* (10 << 4) | (3 << 2) | (1 << 1) | 1; */
		/* SoundData */
		/*
		 * [1B] AACPacketType: 0: AAC Sequence Header
		 */
		*packet_body++ = 0x00;
		/*
		 * [1B] AAC Sequence Header
		 */
		memcpy(packet_body, aacmetadata->audio_specific_config, aacmetadata->audio_specific_config_len);
	}
END:
	FUNC_EXIT();
	return ret;
}

static int _RTMPSink_SetParameterVideo(struct RTMPSink *rtmp_sink, int parameter_type, const void *parameter, unsigned int parameter_size)
{
	FUNC_ENTER();
	int ret = 0;

	struct VideoMetaData *videometadata = (struct VideoMetaData *)rtmp_sink->video_metadata;
	switch (parameter_type) {
	case PARAMETER_VIDEO_FRAMERATE:
		videometadata->framerate = *(unsigned int *)parameter;
		break;
	default:
		if (parameter_type >= PARAMETER_VIDEO_H264_START && parameter_type < PARAMETER_VIDEO_H264_END) {
			ret = _RTMPSink_SetParameterVideoH264(rtmp_sink, parameter_type, parameter, parameter_size);
		}
		break;
	}

	FUNC_EXIT();
	return ret;
}

static int _RTMPSink_SetParameterAudio(struct RTMPSink *rtmp_sink, int parameter_type, const void *parameter, unsigned int parameter_size)
{
	FUNC_ENTER();
	int ret = 0;

	struct AudioMetaData *audiometadata = (struct AudioMetaData *)rtmp_sink->audio_metadata;
	switch (parameter_type) {
	case PARAMETER_AUDIO_SAMPLERATE:
		audiometadata->samplerate = *(unsigned int *)parameter;
		break;
	case PARAMETER_AUDIO_CHANNELCONFIG:
		audiometadata->channelconfig = *(unsigned int *)parameter;
		break;
	case PARAMETER_AUDIO_SAMPLEBITS:
		audiometadata->samplebits = *(unsigned int *)parameter;
		break;
	default:
		if (parameter_type >= PARAMETER_AUDIO_AAC_START && parameter_type < PARAMETER_AUDIO_AAC_END) {
			ret = _RTMPSink_SetParameterAudioAAC(rtmp_sink, parameter_type, parameter, parameter_size);
		}
		break;
	}

END:
	FUNC_EXIT();
	return ret;
}

int RTMPSink_SetParameter(void *_rtmp_sink, int parameter_type, const void *parameter, unsigned int parameter_size)
{
	FUNC_ENTER();
	int ret = 0;
	struct RTMPSink *rtmp_sink = (struct RTMPSink *)_rtmp_sink;
	if (!rtmp_sink) {
		ERROR_PRINT("rtmp sink is null");
		ret = -EINVAL;
		goto END;
	}

	if (parameter_type < PARAMETER_START || parameter_type >= PARAMETER_END) {
		ERROR_PRINT("invalid parameter type");
		ret = -EINVAL;
		goto END;
	}

	if (parameter == NULL && parameter_size != 0) {
		ERROR_PRINT("invalid parameter");
		ret = -EINVAL;
		goto END;
	}

	if (pthread_mutex_lock(&rtmp_sink->mutex) != 0) {
		ERROR_PRINT("mutex lock failed");
		ret = -1;
		goto END;
	}

	switch (parameter_type) {
	case PARAMETER_TIMESTAMP:
		rtmp_sink->video_metadata->timestamp = *(unsigned int *)parameter;
		rtmp_sink->audio_metadata->timestamp = *(unsigned int *)parameter;
		break;
	default:
		if (parameter_type >= PARAMETER_VIDEO_START && parameter_type < PARAMETER_VIDEO_END) {
			ret = _RTMPSink_SetParameterVideo(rtmp_sink, parameter_type, parameter, parameter_size);
		} else if (parameter_type >= PARAMETER_AUDIO_START && parameter_type < PARAMETER_AUDIO_END) {
			ret = _RTMPSink_SetParameterAudio(rtmp_sink, parameter_type, parameter, parameter_size);
		}
		break;
	}

	if (pthread_mutex_unlock(&rtmp_sink->mutex) != 0) {
		ERROR_PRINT("mutex unlock failed");
		ret = -1;
		goto END;
	}
END:
	FUNC_EXIT();
	return ret;
}

static int _RTMPSink_GetParameterVideo(struct RTMPSink *rtmp_sink, int parameter_type, void *parameter, unsigned int *parameter_size)
{
	FUNC_ENTER();
	int ret = 0;

	FUNC_EXIT();
	return ret;
}

int RTMPSink_GetParameter(void *_rtmp_sink, int parameter_type, void *parameter, unsigned int *parameter_size)
{
	FUNC_ENTER();
	int ret = 0;
	struct RTMPSink *rtmp_sink = (struct RTMPSink *)_rtmp_sink;
	if (!rtmp_sink) {
		ERROR_PRINT("rtmp sink is null");
		ret = -EINVAL;
		goto END;
	}

	if (parameter_type < PARAMETER_START || parameter_type >= PARAMETER_END) {
		ERROR_PRINT("invalid parameter type");
		ret = -EINVAL;
		goto END;
	}

	if (parameter == NULL) {
		ERROR_PRINT("invalid parameter");
		ret = -EINVAL;
		goto END;
	}

	if (pthread_mutex_lock(&rtmp_sink->mutex) != 0) {
		ERROR_PRINT("mutex lock failed");
		ret = -1;
		goto END;
	}

	switch (parameter_type) {
	case PARAMETER_TIMESTAMP:
		if (parameter_size && *parameter_size >= sizeof(unsigned int)) {
			*(unsigned int *)parameter = rtmp_sink->video_metadata->timestamp;
			*parameter_size = sizeof(unsigned int);
		}
		break;
	default:
		if (parameter_type >= PARAMETER_VIDEO_START && parameter_type < PARAMETER_VIDEO_END) {
			ret = _RTMPSink_GetParameterVideo(rtmp_sink, parameter_type, parameter, parameter_size);
		}
		break;
	}

	if (pthread_mutex_unlock(&rtmp_sink->mutex) != 0) {
		ERROR_PRINT("mutex unlock failed");
		ret = -1;
		goto END;
	}
END:
	FUNC_EXIT();
	return ret;
}

static int _RTMPSink_WriteVideoH264(struct RTMPSink *rtmp_sink, unsigned char *nalu, unsigned int nalu_size, int flags, void *extra)
{
	FUNC_ENTER();

	unsigned char *payload = NULL;
	unsigned int payload_size = 0;
	unsigned int video_media_packet_size = 0;
	unsigned int is_key_frame = 0;
	unsigned char *packet_body = NULL;

	int ret = 0;
	if (!(flags & RTMP_FLAG_SINGLE_NALU)) {
		ERROR_PRINT("not support");
		ret = -EINVAL;
		goto END;
	}

	payload = (unsigned char *)nalu;
	payload_size = nalu_size;

	if (flags & RTMP_FLAG_INCLUDE_START_CODE) {
		if (payload[0] == 0x00 && payload[1] == 0x00 && ((payload[2] == 0x00 && payload[3] == 0x01) || (payload[2] == 0x01))) {
			payload += (3 + (payload[2] == 0x00));
			payload_size -= (3 + (payload[2] == 0x00));
		} else {
			ERROR_PRINT("invalid start code");
			ret = -1;
			goto END;
		}
	}

	video_media_packet_size = sizeof(RTMPPacket) + RTMP_MAX_HEADER_SIZE + payload_size + 9;
	if (rtmp_sink->video_media_packet_size < video_media_packet_size) {
		rtmp_sink->video_media_packet = (RTMPPacket *)realloc(rtmp_sink->video_media_packet, video_media_packet_size);
		if (!rtmp_sink->video_media_packet) {
			ERROR_PRINT("no memory");
			rtmp_sink->video_media_packet_size = 0;
			goto END;
		}
		rtmp_sink->video_media_packet_size = video_media_packet_size;
		/* update fields */
		rtmp_sink->video_media_packet->m_body = (char *)rtmp_sink->video_media_packet + sizeof(RTMPPacket) + RTMP_MAX_HEADER_SIZE;
		rtmp_sink->video_media_packet->m_hasAbsTimestamp = 0;
		rtmp_sink->video_media_packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;
		rtmp_sink->video_media_packet->m_nInfoField2 = rtmp_sink->rtmp->m_stream_id;
		rtmp_sink->video_media_packet->m_nChannel = 0x04;
		rtmp_sink->video_media_packet->m_headerType = RTMP_PACKET_SIZE_LARGE;

		/* AVCVIDEOPACKET */
		/*
		 * [8 b] AVPacket Type: AVC NALU
		 * [24b] Composition Time: 0
		 */
		rtmp_sink->video_media_packet->m_body[1] = 0X01;
		rtmp_sink->video_media_packet->m_body[2] = 0X00;
		rtmp_sink->video_media_packet->m_body[3] = 0X00;
		rtmp_sink->video_media_packet->m_body[4] = 0X00;
	}
	/* update fields for each frame */
	if ((flags & RTMP_FLAG_INCLUDE_TIMESTAMP) && extra) {
		rtmp_sink->video_media_packet->m_nTimeStamp = *(unsigned int *)extra;
	} else {
		rtmp_sink->video_media_packet->m_nTimeStamp = _Clock(0);
	}
	rtmp_sink->video_media_packet->m_nTimeStamp -= rtmp_sink->video_metadata->timestamp;

	is_key_frame = (*nalu & 0x1F) == 0x05;
	packet_body = (unsigned char *)rtmp_sink->video_media_packet->m_body;
	/* VIDEODATA */
	/*
	 * [4b] FrameType: key frame(1), inter frame(2)
	 * [4b] Codec ID: AVC
	 */
	if (is_key_frame) {
		*packet_body++ = 0x17;
	} else {
		*packet_body++ = 0x27;
	}
	/* AVCVIDEOPACKET */
	/*
	 * [8 b] AVPacket Type: AVC NALU
	 * [24b] Composition Time: 0
	 */
	packet_body += 4;
	/* NALU */
	/* nalu size */
	*packet_body++ = (payload_size >> 24) & 0xFF;
	*packet_body++ = (payload_size >> 16) & 0xFF;
	*packet_body++ = (payload_size >> 8) & 0xFF;
	*packet_body++ = (payload_size >> 0) & 0xFF;
	/* nalu data */
	memcpy(packet_body, payload, payload_size);

	/* update body size for frame */
	rtmp_sink->video_media_packet->m_nBodySize = payload_size + 9;

	if (!RTMP_IsConnected(rtmp_sink->rtmp)) {
		ERROR_PRINT("rtmp is not connected");
		ret = -1;
		goto END;
	}

	if (is_key_frame) {
		if (rtmp_sink->video_first_packet && rtmp_sink->video_metadata_packet) {
			if (!RTMP_SendPacket(rtmp_sink->rtmp, rtmp_sink->video_metadata_packet, TRUE)) {
				ERROR_PRINT("send metadata packet failed");
				ret = -1;
				goto END;
			}
			rtmp_sink->video_first_packet = 0;
		}
	}

	if ((*nalu & 0x1F) == 0x07 || (*nalu & 0x1F) == 0x08) {
		goto END;
	}

	if (!rtmp_sink->video_first_packet) {
		if (!RTMP_SendPacket(rtmp_sink->rtmp, rtmp_sink->video_media_packet, TRUE)) {
			ERROR_PRINT("send media packet failed");
			ret = -1;
			goto END;
		}
	}

END:
	FUNC_EXIT();
	return ret;
}

static int _RTMPSink_WriteVideo(struct RTMPSink *rtmp_sink, unsigned char *payload, unsigned int payload_size, int flags, void *extra)
{
	FUNC_ENTER();

	int ret = 0;
	if (!rtmp_sink) {
		ERROR_PRINT("rtmp sink is null");
		ret = -EINVAL;
		goto END;
	}

	switch (rtmp_sink->video_metadata->codec_type) {
	case CODECTYPE_VIDEO_H264:
		ret = _RTMPSink_WriteVideoH264(rtmp_sink, payload, payload_size, flags, extra);
		break;
	default:
		ERROR_PRINT("unsupported video codec");
		goto END;
	}
END:
	FUNC_EXIT();
	return ret;
}

static int _RTMPSink_WriteAudioAAC(struct RTMPSink *rtmp_sink, unsigned char *payload, unsigned int payload_size, int flags, void *extra)
{
	FUNC_ENTER();

	unsigned int audio_media_packet_size = 0;
	unsigned int is_key_frame = 0;
	unsigned char *packet_body = NULL;

	int ret = 0;

	if (flags & RTMP_FLAG_INCLUDE_START_CODE) {
		/* skip header */
		payload += 7;
		payload_size -= 7;
	}

	audio_media_packet_size = sizeof(RTMPPacket) + RTMP_MAX_HEADER_SIZE + payload_size + 2;
	if (rtmp_sink->audio_media_packet_size < audio_media_packet_size) {
		rtmp_sink->audio_media_packet = (RTMPPacket *)realloc(rtmp_sink->audio_media_packet, audio_media_packet_size);
		if (!rtmp_sink->audio_media_packet) {
			ERROR_PRINT("no memory");
			rtmp_sink->audio_media_packet_size = 0;
			goto END;
		}
		rtmp_sink->audio_media_packet_size = audio_media_packet_size;

		/* update fields */
		rtmp_sink->audio_media_packet->m_body = (char *)rtmp_sink->audio_media_packet + sizeof(RTMPPacket) + RTMP_MAX_HEADER_SIZE;
		rtmp_sink->audio_media_packet->m_hasAbsTimestamp = 0;
		rtmp_sink->audio_media_packet->m_packetType = RTMP_PACKET_TYPE_AUDIO;
		rtmp_sink->audio_media_packet->m_nInfoField2 = rtmp_sink->rtmp->m_stream_id;
		rtmp_sink->audio_media_packet->m_nChannel = 0x04;
		rtmp_sink->audio_media_packet->m_headerType = RTMP_PACKET_SIZE_LARGE;

		/* AUDIODATA */
		/*
		 * [4b] SoundFormat: 10: AAC
		 * [2b] SoundRate: 3: 44 kHz
		 * [1b] SoundSize: 1: snd16Bits
		 * [1b] SoundType: 1: sndStereo
		 */
		rtmp_sink->audio_media_packet->m_body[0] = 0xAF;
		/* SoundData */
		/*
		 * [1B] AACPacketType: 1: Raw Data
		 */
		rtmp_sink->audio_media_packet->m_body[1] = 0x01;
	}
	/* update fields for each frame */
	if ((flags & RTMP_FLAG_INCLUDE_TIMESTAMP) && extra) {
		rtmp_sink->audio_media_packet->m_nTimeStamp = *(unsigned int *)extra;
	} else {
		rtmp_sink->audio_media_packet->m_nTimeStamp = _Clock(1);
	}
	rtmp_sink->audio_media_packet->m_nTimeStamp -= rtmp_sink->audio_metadata->timestamp;

	packet_body = (unsigned char *)rtmp_sink->audio_media_packet->m_body + 2;

	/* RAWDATA */
	memcpy(packet_body, payload, payload_size);

	/* update body size for frame */
	rtmp_sink->audio_media_packet->m_nBodySize = payload_size + 2;

	if (!RTMP_IsConnected(rtmp_sink->rtmp)) {
		ERROR_PRINT("rtmp is not connected");
		ret = -1;
		goto END;
	}

	if (rtmp_sink->video_metadata_packet && rtmp_sink->video_first_packet) {
		goto END;
	}

	if (!rtmp_sink->audio_first_packet) {
		goto SEND_RAW_DATA;
	}

	if (rtmp_sink->audio_metadata_packet) {
		if (!RTMP_SendPacket(rtmp_sink->rtmp, rtmp_sink->audio_metadata_packet, TRUE)) {
			ERROR_PRINT("send audio metadata packet failed");
			ret = -1;
			goto END;
		}
		rtmp_sink->audio_first_packet = 0;
	} else {
		goto END;
	}

SEND_RAW_DATA:
	if (!RTMP_SendPacket(rtmp_sink->rtmp, rtmp_sink->audio_media_packet, TRUE)) {
		ERROR_PRINT("send audio media packet failed");
		ret = -1;
		goto END;
	}

END:
	FUNC_EXIT();
	return ret;
}

static int _RTMPSink_WriteAudio(struct RTMPSink *rtmp_sink, unsigned char *payload, unsigned int payload_size, int flags, void *extra)
{
	FUNC_ENTER();

	int ret = 0;
	if (!rtmp_sink) {
		ERROR_PRINT("rtmp sink is null");
		ret = -EINVAL;
		goto END;
	}

	switch (rtmp_sink->audio_metadata->codec_type) {
	case CODECTYPE_AUDIO_AAC:
		ret = _RTMPSink_WriteAudioAAC(rtmp_sink, payload, payload_size, flags, extra);
		break;
	default:
		ERROR_PRINT("unsupported audio codec");
		goto END;
	}

END:
	FUNC_EXIT();
	return ret;
}

int RTMPSink_Write(void *_rtmp_sink, void *payload, unsigned int payload_size, int flags, void *extra)
{
	FUNC_ENTER();

	int ret = 0;
	struct RTMPSink *rtmp_sink = (struct RTMPSink *)_rtmp_sink;
	if (!rtmp_sink) {
		ERROR_PRINT("rtmp sink is null");
		ret = -EINVAL;
		goto END;
	}

	if (pthread_mutex_lock(&rtmp_sink->mutex) != 0) {
		ERROR_PRINT("mutex lock failed");
		ret = -1;
		goto END;
	}

	if (flags & RTMP_FLAG_PAYLOAD_AUDIO) {
		ret = _RTMPSink_WriteAudio((struct RTMPSink *)rtmp_sink, (unsigned char *)payload, payload_size, flags, extra);
	} else {
		ret = _RTMPSink_WriteVideo((struct RTMPSink *)rtmp_sink, (unsigned char *)payload, payload_size, flags, extra);
	}

	if (pthread_mutex_unlock(&rtmp_sink->mutex) != 0) {
		ERROR_PRINT("mutex unlock failed");
		//ret = -1;
		goto END;
	}

END:
	FUNC_EXIT();
	return ret;
}

int RTMPSink_GetVersion()
{
	return RTMPSINK_VERSION;
}